﻿using System;
using System.Collections.Generic;
using System.Linq;
using Finance;
using TodoSoft.Data;
using TodoSoft.Controls;

namespace TodoSoft.Reporting
{
	public class TreeGridReportExecuter
	{
		private readonly Report _report;

		public TreeGridReportExecuter(Report report)
		{
			_report = report;
		}

		public List<TreeGridRow> FillTreeGrid(ISqlCommandProcessor sql)
		{
			return FillTreeGrid(sql, null);
		}

		public List<TreeGridGroup> Groups { get; private set; }

		public void CreateGroups()
		{
			if (_report.Groupings.Count == 0)
				throw new Exception("Report must have at least one grouping.");

			Groups = new List<TreeGridGroup>();
			//Create groups
			var lastGrouping = _report.Groupings[_report.Groupings.Count - 1];

			foreach (var reportGrouping in _report.Groupings)
			{
				var group = new TreeGridGroup();
				if (reportGrouping != lastGrouping)
				{
					group.AddSwitchColumn(reportGrouping.Expanded);
				}

				foreach (var reportField in reportGrouping.Fields)
				{
					var column = group.AddColumn(reportField.Caption, reportField.Width);
					
					if (reportField.FormatArgument != null)
						column.FormatString = reportField.FormatArgument;

					if (reportField.StringFormat != null)
						column.StringFormat = reportField.StringFormat;
					if (reportField.Padding != null)
						column.Padding = reportField.Padding;

					column.ShrinkToFit = reportField.ShrinkToFit;

                    var rf = reportField;                   
                    column.CellClick += cell => rf.OnCellClick(cell.Row.Data);
				}

				group.HeaderRow.Visible = reportGrouping.HeaderRowVisible;
				if (reportGrouping.Font != null)
					group.Font = reportGrouping.Font;

				Groups.Add(group);
			}
		}

		public List<TreeGridRow> FillTreeGrid(ISqlCommandProcessor sql, Dictionary<ReportParameter, object> getParametersValues)
		{
			var result = new List<TreeGridRow>();
			var parameters = getParametersValues == null? null :CreateDbParameters(getParametersValues);
			var lastGrouping = _report.Groupings[_report.Groupings.Count - 1];

			using (var dataReader = sql.ExecuteReader(_report.DataQuery, parameters))
			{
				while (dataReader.Read())
				{
					var targetChildrenList = result;
					for (int index = 0; index < _report.Groupings.Count; index++)
					{
						var grouping = _report.Groupings[index];
						bool parentNodeExpanded = index == 0 || _report.Groupings[index - 1].Expanded;

						var parentRow = targetChildrenList.FirstOrDefault(r => r.Data[grouping.GroupingField].Equals(dataReader[grouping.GroupingField]));
						if (parentRow == null)
						{
							parentRow = CreateRow(
								targetChildrenList, 
								Groups[index], 
								grouping, 
								dataReader, 
								grouping != lastGrouping, 
								parentNodeExpanded);
						}
						targetChildrenList = parentRow.Children;
					}
				}
			}

			if (_report.Groupings.Count > 1)
				CalculateDelayedFields(0, result);

			return result;
		}

		private Dictionary<string, object> CreateDbParameters(Dictionary<ReportParameter, object> getParametersValues)
		{
			var result = new Dictionary<string, object>();
			foreach (var parametersValue in getParametersValues)
			{
				if (!(parametersValue.Key is PeriodReportParameter))
					throw new Exception("Unknown parameter type");
				
				var interval = (DateTimeInterval) parametersValue.Value;
				result.Add(parametersValue.Key.Name + "_start", interval.Start);
				result.Add(parametersValue.Key.Name + "_end", interval.End);
			}
			return result;
		}

		private void CalculateDelayedFields(int groupingIndex, IList<TreeGridRow> rows)
		{
			var grouping = _report.Groupings[groupingIndex];
			var sumFieldsIndexes = 
				Enumerable.Range(0, grouping.Fields.Count).Where(i => grouping.Fields[i] is ReportDataField &&
                    ((ReportDataField)grouping.Fields[i]).Type == ReportDataFieldTypes.Sum ||
					((ReportDataField)grouping.Fields[i]).Type == ReportDataFieldTypes.Min ||
					((ReportDataField)grouping.Fields[i]).Type == ReportDataFieldTypes.Max).ToArray();

			foreach (var treeGridRow in rows)
			{
				foreach (var fieldIndex in sumFieldsIndexes)
				{
					var sumField = (ReportDataField)grouping.Fields[fieldIndex];

					var firstLeaf = GetLeafRows(treeGridRow.Children).FirstOrDefault();
					if (firstLeaf == null) continue;
					var data = firstLeaf.Data[sumField.PropertyName];
					if (data is decimal)
					{
						switch (sumField.Type)
						{
							case ReportDataFieldTypes.Sum:
								treeGridRow.Cells[fieldIndex + 1] = GetLeafRows(treeGridRow.Children).Select(r => (decimal)r.Data[sumField.PropertyName]).Sum();
								break;
							case ReportDataFieldTypes.Min:
								treeGridRow.Cells[fieldIndex + 1] = GetLeafRows(treeGridRow.Children).Select(r => (decimal)r.Data[sumField.PropertyName]).Min();
								break;
							case ReportDataFieldTypes.Max:
								treeGridRow.Cells[fieldIndex + 1] = GetLeafRows(treeGridRow.Children).Select(r => (decimal)r.Data[sumField.PropertyName]).Max();
								break;
							default:
								throw new Exception("Unknown aggregation type");
						}
						
					}
					else if (data is int)
					{
						switch (sumField.Type)
						{
							case ReportDataFieldTypes.Sum:
								treeGridRow.Cells[fieldIndex + 1] = GetLeafRows(treeGridRow.Children).Select(r => (int)r.Data[sumField.PropertyName]).Sum();
								break;
							case ReportDataFieldTypes.Min:
								treeGridRow.Cells[fieldIndex + 1] = GetLeafRows(treeGridRow.Children).Select(r => (int)r.Data[sumField.PropertyName]).Min();
								break;
							case ReportDataFieldTypes.Max:
								treeGridRow.Cells[fieldIndex + 1] = GetLeafRows(treeGridRow.Children).Select(r => (int)r.Data[sumField.PropertyName]).Max();
								break;
							default:
								throw new Exception("Unknown aggregation type");
						}
					}
				}

				var nextGroupIndex = groupingIndex + 1;
				if (nextGroupIndex != _report.Groupings.Count - 1)
				{
					CalculateDelayedFields(nextGroupIndex, treeGridRow.Children);
				}
			}
		}

		private IEnumerable<TreeGridRow> GetLeafRows(IEnumerable<TreeGridRow> treeGridRow)
		{
			foreach (var gridRow in treeGridRow)
			{
				if (gridRow.Children.Count > 0)
				{
					foreach (var leaf in GetLeafRows(gridRow.Children))
					{
						yield return leaf;
					}
				}
				else
				{
					yield return gridRow;
				}
			}
			
		}

		private TreeGridRow CreateRow(
			IList<TreeGridRow> targetList,
			TreeGridGroup treeGridGroup, 
			ReportGrouping grouping, 
			System.Data.IDataReader dataReader, 
			bool containsSwitchColumn,
			bool parentNodeExpanded)
		{
			var values = new object[grouping.Fields.Count + (containsSwitchColumn ? 1 : 0)];
			if (containsSwitchColumn)
				values[0] = grouping.Expanded ? "-" : "+";

			var row = new TreeGridRow(treeGridGroup, values) {Expanded = parentNodeExpanded};

			for (int index = 0; index < grouping.Fields.Count; index++)
			{
				var field = grouping.Fields[index];

				var reportDataField = field as ReportDataField;
				if (reportDataField != null)
				{
					int cellIndex = index + (containsSwitchColumn ? 1 : 0);
					switch (reportDataField.Type)
				    {
				        case ReportDataFieldTypes.First:
							row.Cells[cellIndex] = dataReader[reportDataField.PropertyName];
							break;
                        case ReportDataFieldTypes.Sum:
						case ReportDataFieldTypes.Min:
						case ReportDataFieldTypes.Max:
							row.Cells[cellIndex] = 0;
						    break;
						case ReportDataFieldTypes.RunningValue:
							if (targetList.Count == 0)
								row.Cells[cellIndex] =
									dataReader[reportDataField.PropertyName];
							else
							{
								var prevRow = targetList[targetList.Count - 1];
								if (prevRow.Cells[cellIndex] is decimal)
								{
									row.Cells[cellIndex] =
										(decimal)prevRow.Cells[cellIndex] +
										(decimal)dataReader[reportDataField.PropertyName];
								}
								else if(prevRow.Cells[cellIndex] != DBNull.Value)
								{
									throw new NotImplementedException("Only decimal run values is implemented");
								}
							}
						    break;
						default:
							throw new Exception("Invalid data field type");
				    }
				}
				else
				{
				    throw new Exception("Invalid field data type");
				}
			}

			var dict = new Dictionary<string, object>();
			for (int lp = 0; lp < dataReader.FieldCount; lp++)
			{
				dict.Add(dataReader.GetName(lp), dataReader.GetValue(lp));
			}
			row.Data = dict;

			//row.Data = Enumerable.Range(0, dataReader.FieldCount)
			//				 .ToDictionary<string, object>(r => r.GetName(), dataReader.GetValue);

            targetList.Add(row);
			return row;
		}

		public void InitParameters(SqlCommandProcessorBase sql)
		{
			foreach (var reportParameter in _report.Parameters)
			{
				reportParameter.Sql = sql;
			}
		}
	}
}

